#include <stdexcept>			// Exception handling
#include <malloc.h>			// Memory allocation
#include "Output.h"

/*! Sets the resolution off the output image ( in this case the bitmap )
 *  @param width the width off the bitmap image
 *  @param height the height off the bitmap image
 *  @return None
 *  Known issues in the code:
 *  - can't use sizeof( BMPHeader ), the compiler will return 16 instead of 14
 *    this is due to the padding bits added by the compiler to make the structure
 *    a multiple of 4. ( SIMP optimalisations ) This problem can change when compiling
 *    with different compilation settings and on different hardware configurations 
 *    ( 32/64 bits CPU's). To ensure that the bitmaps are correctly written to the disk
 *    both sizeof( DIBHeader ) ( should be multiple off 4 on most systems ) and 
 *    sizeof( BMPHeader ) have been omitted and predefined constant values are in use.
 *********************************************/
void bmpOutput::setResolution( const uint32& width, const uint32& height )
{
	if (( width < 1 ) || ( height < 1 )) {
		throw( std::logic_error("Negative height or width passed to setResolution!"));
	}

	if ( data ) free( data );
	if ( bmpHead ) free( bmpHead );
	if ( dibHead ) free( dibHead );
	
	bmpHead = (BMPHeader*)malloc( sizeof( BMPHeader ));
	dibHead = (DIBHeader*)malloc( sizeof( DIBHeader ));
	data = (Color*)malloc( sizeof( Color )*width*height );
	
	bmpHead->bfType = 19778;					// Bitmap identification constant
	bmpHead->bfSize = (width - 4*( width/4 ))*height + width*height*3 + SIZEOF_BMPHeader
			 		+ SIZEOF_DIBHeader;				// size of the file	
	bmpHead->bfReserved1 = 0;							// std value 0
	bmpHead->bfReserved2 = 0;							// std value 0
	bmpHead->bfOffBits = SIZEOF_BMPHeader+SIZEOF_DIBHeader;	// image data offset
	
	dibHead->biSize = SIZEOF_DIBHeader;			// Should be 40 (bytes)
	dibHead->biWidth = width;
	dibHead->biHeight = height;
	dibHead->biPlanes = 1;
	dibHead->biBitCount = 24;				// 24 bits / pixel
	dibHead->biCompression = 0;				// no compression
	dibHead->biSizeImage = 0;
	dibHead->biXPelsPerMeter = 0;
	dibHead->biYPelsPerMeter = 0;
	dibHead->biClrUsed = 0;
	dibHead->biClrImportant = 0;
	
	clear();
}
/*********************************************/
void bmpOutput::setOutputFile( const std::string& file  )
{
	ofile = file;
}
/*********************************************/
void bmpOutput::setPixel( const uint32& width, const uint32& height, const Color& p )
{
	data[width+(dibHead->biWidth*height)] = p;
}
/*********************************************/
void bmpOutput::clear()
{
	sint32 imageSize = dibHead->biHeight*dibHead->biWidth;
	for ( sint32 i = 0; i<imageSize; i++ ) {
		data[i].setGreyScale(255);
	}
}
/*********************************************/
void bmpOutput::flush()
{
	// Acquire file resource
	FILE		*f=0;
		
	f = fopen( ofile.c_str(), "wb" );
	if ( f == 0 ) {
		throw( std::logic_error("Unable to open target output file!"));
	}

	try { 
		// Write the bmp file
		write( f );
		
		// release the resource
		fclose( f );
	}
	catch( ... )
	{
		fclose( f );
		throw;
	}
}
/*********************************************/
void bmpOutput::write( FILE *f )
{
	uint32		writtenBytes=0;
	const uint8	paddingByte=255;
	sint32 		px;
	
	if (( bmpHead == 0 ) || ( dibHead == 0 ) || ( data == 0 )) {
		throw ( std::logic_error("Null valued substructures! Output class not initialized!") );
	}

	// Write the bmp header
	fwrite( &bmpHead->bfType, sizeof( bmpHead->bfType ), 1, f );
	fwrite( &bmpHead->bfSize, sizeof( bmpHead->bfSize ), 1, f );
	fwrite( &bmpHead->bfReserved1, sizeof( bmpHead->bfReserved1 ), 1, f );
	fwrite( &bmpHead->bfReserved2, sizeof( bmpHead->bfReserved2 ), 1, f );
	fwrite( &bmpHead->bfOffBits, sizeof( bmpHead->bfOffBits ), 1, f );

	// Write the dib header
	fwrite( &dibHead->biSize, sizeof( dibHead->biSize ), 1, f );
	fwrite( &dibHead->biWidth, sizeof( dibHead->biWidth ), 1, f );
	fwrite( &dibHead->biHeight, sizeof( dibHead->biHeight ), 1, f );
	fwrite( &dibHead->biPlanes, sizeof( dibHead->biPlanes ), 1, f );
	fwrite( &dibHead->biBitCount, sizeof( dibHead->biBitCount ), 1, f );
	fwrite( &dibHead->biCompression, sizeof( dibHead->biCompression ), 1, f );
	fwrite( &dibHead->biSizeImage, sizeof( dibHead->biSizeImage ), 1, f );
	fwrite( &dibHead->biXPelsPerMeter, sizeof( dibHead->biXPelsPerMeter ), 1, f );
	fwrite( &dibHead->biYPelsPerMeter, sizeof( dibHead->biYPelsPerMeter ), 1, f );
	fwrite( &dibHead->biClrUsed, sizeof( dibHead->biClrUsed ), 1, f );
	fwrite( &dibHead->biClrImportant, sizeof( dibHead->biClrImportant ), 1, f );
	
	for ( sint32 h = dibHead->biHeight-1; h>=0; h-- ) {
		sint32 w;
		for ( w = 0; w<dibHead->biWidth; w++ ) {
			px = (h*dibHead->biWidth) + w;
			
			fwrite( &data[px]['b'], sizeof( uint8 ), 1, f );
			fwrite( &data[px]['g'], sizeof( uint8 ), 1, f );
			fwrite( &data[px]['r'], sizeof( uint8 ), 1, f );
		}
		
		writtenBytes = w*sizeof(Color);
		while (( writtenBytes % 4 ) != 0 ) {
			fwrite( &paddingByte, sizeof( uint8 ), 1, f );
			writtenBytes++;
		}
	}

	return;
}
/*********************************************/
bmpOutput::bmpOutput()
{
	data = 0;
	bmpHead = 0;
	dibHead = 0;
	ofile = "Output.bmp";		// Standard output file
}
/*********************************************/
bmpOutput::~bmpOutput()
{
	if ( data ) free( data );
	if ( bmpHead ) free( bmpHead );
	if ( dibHead ) free( dibHead );
}
/*********************************************/